package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type Code struct {
	op     string
	target string
	source string
}

type Command struct {
	length int8
	op     int8
	target int16
	source int16
}

type Label struct {
	name  string
	index int
}

var label_index = 0
var labels = make(map[int]Label)
var index = 0

func filesize(filename string) (int64, error) {
	fileinfo, err := os.Stat(filename)
	if err != nil {
		return 0, err
	}
	size := fileinfo.Size()
	return size, nil
}

func read_file(filename string) (string, error) {
	var size, size_err = filesize(filename)
	if size_err != nil {
		return "", size_err
	}
	var input_bytes = make([]byte, size)
	var input_file, input_open_err = os.Open(filename)
	if input_open_err != nil {
		return "", input_open_err
	}
	var _, read_err = input_file.Read(input_bytes)
	if read_err != nil {
		_ = input_file.Close()
		return "", read_err
	}
	_ = input_file.Close()
	return string(input_bytes), nil
}

func lexer(input string) (map[int]Code, error) {
	var list = make(map[int]Code)
	var lines = strings.Split(input, ";")
	for _, value := range lines {
		var v = strings.TrimSpace(value)
		if len(v) == 0 {
			continue
		}
		var label_colon_index = strings.Index(v, ":")
		if label_colon_index > 0 {
			labels[label_index] = Label{
				name:  v[:label_colon_index],
				index: index,
			}
			label_index++
		}
		v = strings.TrimSpace(v[label_colon_index+1:])
		var first_space_index = strings.Index(v, " ")
		var first_comma = strings.Index(v, ",")
		var first_quota = strings.Index(v, "\"")
		var op = strings.TrimSpace(v[:first_space_index])
		var target = ""
		if first_space_index > -1 {
			var command = strings.TrimSpace(strings.ToUpper(v[:first_space_index]))
			if command == "DB" {
				var next_quota = strings.Index(v[first_quota+1:], "\"")
				if next_quota == -1 {
					return nil, errors.New("close quotation mark not found")
				}
				var str = v[first_quota+1 : first_quota+1+next_quota]
				var str_len = len(str)
				for i := str_len - 1; i >= 0; i-- {
					var code = Code{
						op:     "push",
						target: "'" + string(str[i]) + "'",
						source: "",
					}
					list[index] = code
					index++
				}
			} else if command == "INCLUDE" || command == "REQUIRE" {
				var next_quota = strings.Index(v[first_quota+1:], "\"")
				if next_quota == -1 {
					return nil, errors.New("close quotation mark not found")
				}
				var str = v[first_quota+1 : first_quota+1+next_quota]
				var str_len = len(str)
				if str_len < 1 {
					return nil, errors.New("include file name cannot be empty")
				}
				var code, code_read_err = read_file(str)
				if code_read_err != nil {
					if command == "REQUIRE" {
						return nil, code_read_err
					} else {
						continue
					}
				}
				var l, l_err = lexer(code)
				if l_err != nil {
					return nil, l_err
				}
				for l_k, l_v := range l {
					list[l_k] = l_v
				}
			} else {
				if first_comma == -1 {
					target = strings.TrimSpace(v[first_space_index+1:])
					var code = Code{
						op:     op,
						target: target,
						source: "",
					}
					list[index] = code
					index++
				} else {
					target = strings.TrimSpace(v[first_space_index+1 : first_comma])
					var source = ""
					if first_comma > -1 {
						source = strings.TrimSpace(v[first_comma+1:])
					}
					var code = Code{
						op:     op,
						target: target,
						source: source,
					}
					list[index] = code
					index++
				}
			}
		}
	}
	return list, nil
}

func str2cmd(str string, current_line int) (uint16, error) {
	if len(str) < 2 {
		return uint16(0xFFFF), errors.New("illegal operator")
	}
	var is_memory = 0
	var is_register = 0
	var is_segment_register = 0
	var op = -1
	var memory_address = 0
	var ret uint16 = 0
	if str[0] == '$' {
		is_register = 1
		var s = strings.ToUpper(str[1:])
		if len(s) < 2 {
			return uint16(0xFFFF), errors.New("illegal register")
		}
		if s[0] == 'R' {
			var register_id, atoi_err = strconv.Atoi(s[1:])
			if atoi_err != nil {
				return uint16(0xFFFF), atoi_err
			}
			if register_id < 0 || register_id > 7 {
				return uint16(0xFFFF), errors.New("illegal register id")
			}
			op = register_id
		} else {
			return uint16(0xFFFF), errors.New("illegal register")
		}
	} else if str[0] == '!' {
		is_memory = 1
		var target_type = str[1]
		if target_type == '$' {
			is_register = 1
			str = str[2:]
			var target = strings.ToUpper(str[1:])
			if target[0] == 'R' {
				var register_id, atoi_err = strconv.Atoi(target[1:])
				if atoi_err != nil {
					return uint16(0xFFFF), atoi_err
				}
				if register_id < 0 || register_id > 7 {
					return uint16(0xFFFF), errors.New("illegal register id")
				}
				op = register_id
			} else {
				return uint16(0xFFFF), errors.New("illegal register")
			}
		} else if target_type == '#' {
			str = str[2:]
			var prefix = str[1]
			var base = 10
			switch prefix {
			case 'b':
				fallthrough
			case 'B':
				base = 2
			case 'o':
				fallthrough
			case 'O':
				base = 8
			case 'd':
				fallthrough
			case 'D':
				base = 10
			case 'x':
				fallthrough
			case 'X':
				base = 16
			}
			var address, atoi_err = strconv.ParseInt(str, base, 16)
			if atoi_err != nil {
				return uint16(0xFFFF), atoi_err
			}
			memory_address = int(address)
		}
	} else if str[0] == '#' {
		str = str[1:]
		var prefix = str[1]
		var base = 10
		switch prefix {
		case 'b':
			fallthrough
		case 'B':
			base = 2
		case 'o':
			fallthrough
		case 'O':
			base = 8
		case 'd':
			fallthrough
		case 'D':
			base = 10
		case 'x':
			fallthrough
		case 'X':
			base = 16
		}
		var address, atoi_err = strconv.ParseInt(str[2:], base, 16)
		if atoi_err != nil {
			return uint16(0xFFFF), atoi_err
		}
		memory_address = int(address)
	} else if str[0] == '@' {
		var s = str[1:]
		if len(s) < 1 {
			return uint16(0xFFFF), errors.New("illegal label")
		}
		for _, value := range labels {
			if value.name == s {
				ret += uint16(value.index-current_line) & 0b0001111111111111
				break
			}
		}
	} else if str[0] == '%' {
		var s = strings.ToUpper(str[1:])
		if len(s) < 1 {
			return uint16(0xFFFF), errors.New("illegal segment register")
		}
		switch s {
		case "BP":
			ret += 0
		case "SP":
			ret += 1
		case "SI":
			ret += 2
		case "DI":
			ret += 3
		case "CS":
			ret += 4
		case "DS":
			ret += 5
		case "SS":
			ret += 6
		case "ES":
			ret += 7
		case "IP":
			ret += 8
		}
		is_segment_register = 1
	} else if str[0] == '\'' {
		var next_index = strings.Index(str[1:], "'")
		if next_index == -1 {
			return uint16(0xFFFF), errors.New("right quotation mark not found")
		} else {
			var ch = str[1 : next_index+1]
			var ch_len = len(ch)
			if ch_len != 1 {
				return uint16(0xFFFF), errors.New("charactor too long")
			} else {
				ret += uint16(ch[0])
			}
		}
	} else {
		return uint16(0xFFFF), errors.New("illegal operator")
	}
	if is_memory == 1 {
		ret += 1 << 14
	}
	if is_register == 1 {
		ret += 1 << 13
		ret += uint16(op) & 0b0001111111111111
	} else {
		ret += uint16(memory_address) & 0b0001111111111111
	}
	if is_segment_register == 1 {
		ret += uint16(1 << 15)
	}
	return ret, nil
}

func code2command(code map[int]Code) (map[int]Command, error) {
	var command_list = make(map[int]Command)
	for key, value := range code {
		value.op = strings.ToUpper(value.op)
		var length int8
		var op int8
		switch value.op {
		case "MOV":
			length = 3
			op = 1
		case "ADD":
			length = 3
			op = 2
		case "SUB":
			length = 3
			op = 3
		case "MUL":
			length = 3
			op = 4
		case "DIV":
			length = 3
			op = 5
		case "SHL":
			length = 3
			op = 6
		case "SHR":
			length = 3
			op = 7
		case "INT":
			length = 2
			op = 8
		case "JMP":
			length = 2
			op = 9
		case "JZ":
			length = 2
			op = 10
		case "JNZ":
			length = 2
			op = 11
		case "CMP":
			length = 3
			op = 12
		case "INC":
			length = 2
			op = 13
		case "DEC":
			length = 2
			op = 14
		case "AND":
			length = 3
			op = 15
		case "OR":
			length = 3
			op = 16
		case "NOT":
			length = 2
			op = 17
		case "XOR":
			length = 3
			op = 18
		case "PUSH":
			length = 2
			op = 19
		case "POP":
			length = 2
			op = 20
		case "DB":
			length = 2
			op = 21
		case "INCLUDE":
			length = 2
			op = 22
		case "REQUIRE":
			length = 2
			op = 23
		}
		var target, target_err = str2cmd(value.target, key)
		if target_err != nil {
			return nil, target_err
		}
		var source uint16 = 0
		if length == 3 {
			var source_err error
			source, source_err = str2cmd(value.source, key)
			if source_err != nil {
				return nil, source_err
			}
		}
		command_list[key] = Command{
			length: length,
			op:     op,
			target: int16(target),
			source: int16(source),
		}
	}
	return command_list, nil
}

func main() {
	if len(os.Args) != 3 {
		fmt.Println("usage: " + os.Args[0] + " input output")
		return
	}
	var input_filename = os.Args[1]
	var output_filename = os.Args[2]

	var size, size_err = filesize(input_filename)
	if size_err != nil {
		fmt.Println("input file get size failed:" + size_err.Error())
		return
	}
	var input_bytes = make([]byte, size)
	var input_file, input_open_err = os.Open(input_filename)
	if input_open_err != nil {
		fmt.Println("input file open failed:" + input_open_err.Error())
		return
	}
	var _, read_err = input_file.Read(input_bytes)
	if read_err != nil {
		_ = input_file.Close()
		fmt.Println("input file read failed:" + read_err.Error())
		return
	}
	_ = input_file.Close()
	var input = string(input_bytes)
	var codes, code_lex_err = lexer(input)
	if code_lex_err != nil {
		fmt.Println("code lex failed:" + code_lex_err.Error())
		return
	}
	var cmd, cmd_err = code2command(codes)
	if cmd_err != nil {
		fmt.Println("conv failed:" + cmd_err.Error())
		return
	}

	var output_file, output_file_err = os.OpenFile(output_filename, os.O_WRONLY|os.O_CREATE, 0777)
	if output_file_err != nil {
		fmt.Println("output file open failed:" + output_file_err.Error())
		return
	}
	var cmd_len = len(cmd)
	for i := 0; i < cmd_len; i++ {
		var value = cmd[i]
		var segment []byte
		if value.length == 3 {
			segment = make([]byte, 6)
			segment[0] = byte(3)
			segment[1] = byte(value.op)
			segment[2] = byte(value.target >> 8)
			segment[3] = byte(value.target & 0b11111111)
			segment[4] = byte(value.source >> 8)
			segment[5] = byte(value.source & 0b11111111)
			for j := 0; j < 6; j++ {
				fmt.Printf("%08b ", segment[j])
			}
			fmt.Println()
		} else if value.length == 2 {
			segment = make([]byte, 4)
			segment[0] = byte(2)
			segment[1] = byte(value.op)
			segment[2] = byte(value.target >> 8)
			segment[3] = byte(value.target & 0b11111111)
			for j := 0; j < 4; j++ {
				fmt.Printf("%08b ", segment[j])
			}
			fmt.Println()
		}
		var _, write_err = output_file.Write(segment)
		if write_err != nil {
			_ = output_file.Close()
			fmt.Println("output file write fail:" + write_err.Error())
			return
		}
	}
	_ = output_file.Close()
	fmt.Println("ok")
}
